/**********************************************************************
  SchoolView.m

  Author:	David C. "Slam" Lambert
  Date:		1 February, 1993
**********************************************************************/
#import <math.h>
#import <string.h>
#import <memory.h>
#import <appkit/Font.h>
#import <appkit/View.h>
#import <appkit/Button.h>
#import <appkit/NXImage.h>
#import <appkit/TextField.h>
#import <appkit/NXColorWell.h>
#import	<defaults/defaults.h>
#import <dpsclient/wraps.h>
#import "Thinker.h"
#import "School.h"
#import "SchoolView.h"

#ifndef FONT_SIZE
#define FONT_SIZE	(20.0)
#endif

static NXDefaultsVector	SchoolViewDefaults = 
	{
		{ "SchoolCount", "40" },
		{ "SchoolDistExp", "3.0" },
		{ "SchoolMomentum", "0.05" },
		{ "SchoolMinRadius", "60.0" },
		{ "SchoolMaxVelocity", "18.0" },
		{ "SchoolMinVelocity", "0.0" },
		{ "SchoolAccLimit", "5.0" },
		{ "SchoolAvoidFact", "40" },
		{ "SchoolMatchFact", "0.7" },
		{ "SchoolCenterFact", "13" },
		{ "SchoolTargetFact", "10" },
		{ "SchoolFollowsMouse", "0" },
		{ "SchoolChangeGoalFreq", "20"},
		{ NULL },
	};

static float
uniform(long seed)
{
    long            k;
    long            z;
    static long     s1;
    static long     s2;

    if (seed != 0)  {
        s1 = seed;
        s2 = ~seed;
    }

    k = s1 / 53668;
    s1 = 40014 * (s1 - k * 53668) - k * 12211;
    if (s1 < 0)
        s1 = s1 + 2147483563;

    k  = s2 / 52774;
    s2 = 40692 * (s2 - k * 52774) - k * 3791;
    if (s2 < 0)
        s2 = s2 + 2147483399;

    z = s1 - s2;
    if (z < 1)
        z = z + 2147483562;

    return(((float)z * 4.656613e-10));
}

@implementation SchoolView

+ initialize
{
	NXRegisterDefaults([NXApp appName], SchoolViewDefaults);
	return self;
}

- getDefaults
{
	const	char	*theName = [NXApp appName];

	schoolCount = atoi(NXGetDefaultValue(theName, "SchoolCount"));
	maxVel = atof(NXGetDefaultValue(theName, "SchoolMaxVelocity"));
	minVel = atof(NXGetDefaultValue(theName, "SchoolMinVelocity"));
	distExp = atof(NXGetDefaultValue(theName, "SchoolDistExp"));
	momentum = atof(NXGetDefaultValue(theName, "SchoolMomentum"));
	accLimit = atof(NXGetDefaultValue(theName, "SchoolAccLimit"));
	avoidFact = atof(NXGetDefaultValue(theName, "SchoolAvoidFact"));
	matchFact = atof(NXGetDefaultValue(theName, "SchoolMatchFact"));
	minRadius = atof(NXGetDefaultValue(theName, "SchoolMinRadius"));
	centerFact = atof(NXGetDefaultValue(theName, "SchoolCenterFact"));
	targetFact = atof(NXGetDefaultValue(theName, "SchoolTargetFact"));
	followMouse = (BOOL)atoi(NXGetDefaultValue(theName, "SchoolFollowsMouse"));
	goalChgFreq = atoi(NXGetDefaultValue(theName, "SchoolChangeGoalFreq"));

	return self;
}

- writeDefaults
{
	char	string[100];
	const	char	*theName = [NXApp appName];

	sprintf(string, "%d", schoolCount);
	NXWriteDefault(theName, "SchoolCount", string);
	sprintf(string, "%.4f", maxVel);
	NXWriteDefault(theName, "SchoolMaxVelocity", string);
	sprintf(string, "%.4f", avoidFact);
	NXWriteDefault(theName, "SchoolAvoidFact", string);
	sprintf(string, "%d", followMouse);
	NXWriteDefault(theName, "SchoolFollowsMouse", string);

	return self;
}

- initFrame:(const NXRect *)frameRect
{
	[super initFrame:frameRect];
	[self setOpaque:YES];
	[self setClipping:NO];

	uniform(time(NULL));
	theSchools = (BOID *)malloc(0);
	oldString = malloc(0);
	schoolString = malloc(0);
	coords = (float *)malloc(0);
	oldCoords = (float *)malloc(0);

	[self getDefaults];
	[self allocateGState];
	[self setValues];
	[self setup];
	PSWDavesDefFont("Fish");
	PSselectfont("Fish", FONT_SIZE);

	return self;
}

- free
{
	free(theSchools);
	free(oldString);
	free(schoolString);
	free(coords);
	free(oldCoords);

	[targetImage free];
	return [super free];
}

- setValues
{
	[countField setIntValue:schoolCount];
	[countSlider setIntValue:schoolCount];
	[vMaxField setIntValue:maxVel];
	[vMaxSlider setIntValue:maxVel];
	[avoidField setIntValue:avoidFact];
	[avoidSlider setIntValue:avoidFact];
	[followSwitch setState:followMouse];
	return self;
}

- awakeFromNib
{
	[self setValues];
	targetImage = [NXImage findImageNamed:"Target"];
	distComp = FONT_SIZE * 0.707;
	return self;
}

- takeValues:sender
{
	schoolCount = [countSlider floatValue];
	maxVel = [vMaxSlider floatValue];
	avoidFact = [avoidSlider floatValue];
	followMouse = [followSwitch state];
	targetFact = (followMouse) ? 200.0 : 10.0;

	[countField setIntValue:schoolCount];
	[vMaxField setIntValue:maxVel];
	[avoidField setIntValue:avoidFact];

	[self setup];
	[self writeDefaults];

	return self;
}

- setup
{
	int		i;
	int		j;
	BOID	*b;

	theSchools = realloc(theSchools, sizeof(BOID) * schoolCount);

	oldString = (char *)realloc(oldString, sizeof(char) * (schoolCount + 1));
	schoolString = (char *)realloc(schoolString, sizeof(char) * (schoolCount + 1));
	coords = (float *)realloc(coords, sizeof(float) * schoolCount * 2);
	oldCoords = (float *)realloc(oldCoords, sizeof(float) * schoolCount * 2);

	bzero(coords, sizeof(float) * schoolCount * 2);
	bzero(oldCoords, sizeof(float) * schoolCount * 2);

	memset(oldString, 'A', schoolCount);
	memset(schoolString, '0', schoolCount);
	oldString[schoolCount] = schoolString[schoolCount] = '\0';

	for(i = 0, j = -2, b = theSchools; i < schoolCount; i++, b++, j+=2)	{
		BOID_X(b) = NX_X(&frame) + uniform(0) * NX_WIDTH(&frame);
		BOID_Y(b) = NX_Y(&frame) + uniform(0) * NX_HEIGHT(&frame);
		BOID_XVEL(b) = 2.0 * (uniform(0)-0.5) * maxVel;
		BOID_YVEL(b) = 2.0 * (uniform(0)-0.5) * maxVel;
		BOID_XACC(b) = BOID_YACC(b) = 0.0;
		if (i > 0)	{
			coords[j] = (BOID_X(b) - BOID_X(b-1));
			coords[j+1] = (BOID_Y(b) - BOID_Y(b-1));
		}
	}
	goalPoint.x = NX_MIDX(&frame); goalPoint.y = NX_MIDY(&frame);

	if ([[self window] canStoreColor])
		hasColor = YES;

	[self display];
	return self;
}

- inspector:sender
{
    char buf[1024];

	if (!thePanel)	{
		sprintf(buf, "%s/SchoolView.nib", [sender moduleDirectory:"School"]);
		[NXApp loadNibFile:buf owner:self withNames:NO];
	}
	[self getDefaults];
	return thePanel;
}

- (BOOL)ignoreMouseMovement
{
	return followMouse;
}

- computeAccelerations
{
	int		i;
	int		j;
	float	cAx;
	float	cAy;
	float	aVx;
	float	aVy;
	float	dist;
	float	aMag;
	float	xDiff;
	float	yDiff;
	float	adjDist;
	float	adjDistSum;
	BOID	*b0;
	BOID	*b1;
	NXPoint	tmpPoint;
	static unsigned counter;

	adjDist = 0;
	if (window != nil && followMouse)	{
		[window getMouseLocation:&tmpPoint];
		if (NXMouseInRect(&tmpPoint, &frame, NO))	{
			goalPoint = tmpPoint;
			goalPoint.x -= 10.0; goalPoint.y -= 16.0;
		}
	}
	else if (!((++counter) % goalChgFreq))	{
		goalPoint.x = NX_MIDX(&frame) + (uniform(0)-0.5) * 0.45 * NX_WIDTH(&frame);
		goalPoint.y = NX_MIDY(&frame) + (uniform(0)-0.5) * 0.45 * NX_HEIGHT(&frame);
	}
	/* other school avoidance */
	for(i = 0, b0 = theSchools; i < schoolCount; i++, b0++)	{
		adjDistSum = 0.0;
		cAx = cAy = aVx = aVy = 0.0;
		BOID_XACC(b0) = BOID_YACC(b0) = 0.0;
		for(j = 0, b1 = theSchools; j < schoolCount; j++, b1++)	{
			if (b1 == b0) continue;
			xDiff = XDIFF(b0, b1);
			yDiff = YDIFF(b0, b1);
			if (xDiff > NX_MIDX(&frame))
				xDiff = NX_MAXX(&frame) - xDiff;
			if (yDiff > NX_MIDY(&frame))
				yDiff = NX_MAXY(&frame) - yDiff;
			dist = NORM(xDiff, yDiff) - distComp;
			if (dist > minRadius) continue;
			else if (dist <= 0.0) dist = MIN_DIST;
			adjDist = dist * dist;
			adjDistSum += (1.0 / adjDist);
			xDiff /= adjDist; yDiff /= adjDist;
			cAx -= xDiff; cAy -= yDiff;
			BOID_XACC(b0) += xDiff;	BOID_YACC(b0) += yDiff;
			aVx += (BOID_XVEL(b1) / adjDist); aVy += (BOID_YVEL(b1) / adjDist);
		}
		xDiff = goalPoint.x - BOID_X(b0); yDiff = goalPoint.y - BOID_Y(b0);
		BOID_XACC(b0) *= avoidFact;	BOID_YACC(b0) *= avoidFact;
		aMag = NORM(BOID_XACC(b0), BOID_YACC(b0));
		/* velocity matching */
		if (adjDistSum != 0.0 && aMag < accLimit)	{
			aVx /= adjDistSum; aVy /= adjDistSum;
			BOID_XACC(b0) += ((aVx - BOID_XVEL(b0)) * matchFact);
			BOID_YACC(b0) += ((aVy - BOID_YVEL(b0)) * matchFact);
			aMag = NORM(BOID_XACC(b0), BOID_YACC(b0));
			/* flock centering */
			if (aMag < accLimit)	{
				BOID_XACC(b0) += cAx * centerFact;
				BOID_YACC(b0) += cAy * centerFact;
				aMag = NORM(BOID_XACC(b0), BOID_YACC(b0));
				/* target attraction */
				if (aMag < accLimit)	{
					BOID_XACC(b0) += xDiff * targetFact / adjDist;
					BOID_YACC(b0) += yDiff * targetFact / adjDist;
				}

			}
		}
		BOID_XACC(b0) += (uniform(0)-0.5);
		BOID_YACC(b0) += (uniform(0)-0.5);
		aMag = NORM(BOID_XACC(b0), BOID_YACC(b0));
		if (aMag > accLimit)	{
			BOID_XACC(b0) *= sqrt(accLimit/aMag);
			BOID_YACC(b0) *= sqrt(accLimit/aMag);
		}
	}
		
	return self;
}

- (BOOL)useBufferedWindow
{
	return NO;
}

- didLockFocus
{
	PSselectfont("Fish", FONT_SIZE);
	return self;
}

- oneStep
{
	int		i;
	int		j;
	int		index;
	float	avgIndex;
	BOID	*b;
	float	vMag;
	float	oldX0 = BOID_X(theSchools);
	float	oldY0 = BOID_Y(theSchools);
	NXColor	theColor;
	static float	hue;

	bcopy(coords, oldCoords, sizeof(float)*schoolCount*2);
	bcopy(schoolString, oldString, sizeof(char)*schoolCount);

	PSnewinstance();
	PSsetinstance(YES);
	if (followMouse)
		[targetImage composite:NX_COPY toPoint:&goalPoint];
	PSsetinstance(NO);

	avgIndex = 0;
	for(i = 0, j = -2, b = theSchools; i < schoolCount; i++, b++, j+=2)	{
		/* apply accelerations */
		BOID_XVEL(b) = BOID_XACC(b) + (1.0 + momentum) * BOID_XVEL(b);
		BOID_YVEL(b) = BOID_YACC(b) + (1.0 + momentum) * BOID_YVEL(b);
		vMag = 1.0e-6 + NORM(BOID_XVEL(b), BOID_YVEL(b));
		if (vMag > maxVel)	{
			BOID_XVEL(b) *= (maxVel/vMag);
			BOID_YVEL(b) *= (maxVel/vMag);
		}
		else if (vMag < minVel)	{
			BOID_XVEL(b) *= (minVel/vMag);
			BOID_XVEL(b) *= (minVel/vMag);
		}

		/* apply movements */
		BOID_X(b) += BOID_XVEL(b);
		BOID_X(b) = (BOID_X(b) > NX_MAXX(&frame)) ? 0.0 : BOID_X(b);
		BOID_X(b) = (BOID_X(b) < NX_X(&frame)) ? NX_MAXX(&frame) : BOID_X(b);

		BOID_Y(b) += BOID_YVEL(b);
		BOID_Y(b) = (BOID_Y(b) > NX_MAXY(&frame)) ? 0.0 : BOID_Y(b);
		BOID_Y(b) = (BOID_Y(b) < NX_Y(&frame)) ? NX_MAXY(&frame) : BOID_Y(b);

		if (i > 0)	{
			coords[j] = (BOID_X(b) - BOID_X(b-1));
			coords[j+1] = (BOID_Y(b) - BOID_Y(b-1));
		}
		if (fabs(fabs(BOID_XVEL(b))-fabs(BOID_YVEL(b))) < maxVel/2.0)	{
			if (BOID_XVEL(b) < 0.0)
				index = (BOID_YVEL(b) > 0.0) ? '1' : '2';
			else
				index = (BOID_YVEL(b) > 0.0) ? '4' : '3';
		}
		else if (fabs(BOID_XVEL(b)) >  fabs(BOID_YVEL(b)))
			index = (BOID_XVEL(b) < 0.0) ? 'l' : 'r';
		else
			index = (BOID_YVEL(b) > 0.0) ? 'u' : 'd';
		schoolString[i] = index;
	}
	NXSetColor(NX_COLORBLACK);
	PSWDavesXYShow(oldX0, oldY0,
			  oldString, oldCoords, schoolCount*2);

	if (hasColor)	{
		theColor = NXConvertHSBToColor(hue/360.0, 1.0, 1.0);
		hue = (hue < 360.0) ? hue+1.0 : 0.0;
		NXSetColor(theColor);
	}
	else
		NXSetColor(NX_COLORWHITE);

	PSWDavesXYShow(BOID_X(theSchools), BOID_Y(theSchools),
			  schoolString, coords, schoolCount*2);
	[self computeAccelerations];
	return self;
}

- (const char *)windowTitle
{
	return "School        by David Lambert";
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self setup];
	return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
	if (!rects || !rectCount) return self;
	PSselectfont("Fish", FONT_SIZE);
	NXSetColor(NX_COLORBLACK);
	NXRectFill(rects);
	return self;
}

@end
